#include "md5_parser.h"

#include <sstream>

using namespace krryn::data;
using namespace krryn::math;

md5_parser::md5_parser(const std::string &a_Filename, size_t a_MemoryBudgetBytes)
	: m_Lexer(a_Filename), m_MemoryBudget(a_MemoryBudgetBytes), m_BytesLeft(a_MemoryBudgetBytes)
{
}

void md5_parser::header(){
	move(); // move in first token
	match(version);
	if(m_Look.m_String != "10"){
		error("invalid file version, expecting 10, got " + m_Look.m_String);
	}else{
		match(integer);
	}

	// ignore commandline options for now, they're engine specific
	match(commandline); match(string);
}

vector3f md5_parser::vector(){
	math::vector3f l_Result;

	match(open_set);

	l_Result.m_X = m_Look.as_float();
	match(real);

	l_Result.m_Y = m_Look.as_float();
	match(real);

	l_Result.m_Z = m_Look.as_float();
	match(real);

	match(close_set);

	return l_Result;
}

quaternionf md5_parser::quat(){
	float l_TempX, l_TempY, l_TempZ;

	match(open_set);

	l_TempX = m_Look.as_float();
	match(real);

	l_TempY = m_Look.as_float();
	match(real);

	l_TempZ = m_Look.as_float();
	match(real);

	match(close_set);

	return quaternionf::implicit_w(l_TempX, l_TempY, l_TempZ);
}

void md5_parser::match_number(){
	if(m_Look.m_Tag == real || m_Look.m_Tag == integer){
		move();
	}else{
		std::stringstream l_Stream;
		l_Stream
			<< "unexpected token '" 
			<< m_Look.m_Tag 
			<< "', expecting '" 
			<< tag_to_string(real)
			<< "' or '"
			<< tag_to_string(integer)
			<< "'";

		error(l_Stream.str());
	}
}

void md5_parser::match(md5_tags a_Tag){
	if(a_Tag == real){
		// integers can be propagated to floats if needed
		match_number();
	}else{
		if(m_Look.m_Tag == a_Tag){
			move();
		}else{
			unexpected(a_Tag, m_Look.m_Tag);
		}
	}
}

void md5_parser::unexpected(md5_tags a_Expected, md5_tags a_Current){
	std::string l_Current = tag_to_string(a_Current);

	if(!l_Current.empty()){
		error(
			  "unexpected token '" 
			+ l_Current 
			+ "', expecting '" 
			+ tag_to_string(a_Expected)
			+ "'"
		);
	}else{
		error(
			  "expecting token '"
			+ tag_to_string(a_Expected)
			+ "'"
		);
	}
}

void md5_parser::move(){
	m_Look = m_Lexer.scan();
}

void md5_parser::request_allocation(size_t a_Size){
	if(m_BytesLeft < a_Size){
		std::stringstream l_Stream;
		l_Stream 
			<< "Out of memory while trying to allocate "
			<< a_Size
			<< " bytes, memory limit is at "
			<< m_MemoryBudget
			<< " bytes. You have "
			<< m_BytesLeft 
			<< " bytes remaining.";

		throw md5_parse_error(l_Stream.str());
	}

	m_BytesLeft -= a_Size;
}

void md5_parser::error(const std::string &a_Error){
	std::stringstream l_Stream;
	l_Stream
		<< "Error: " << a_Error 
		<< " on line " << m_Lexer.line() << ".";

	throw md5_syntax_error(l_Stream.str());
}
